package com.devin.utils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Class 工具类
 *
 * @author devin
 */
public class ClassUtils {
    private ClassUtils () {};

    /**
     * {@link #scan(String, boolean)} 方法的重载
     *
     * @param packageName 指定包名
     */
    public static Set<Class<?>> scan(String packageName) {
        return scan(packageName, true);
    }

    /**
     * 扫描并加载指定包下的类
     *
     * @param packageName 指定包名
     * @param recursive 是否递归加载
     */
    public static Set<Class<?>> scan(String packageName, boolean recursive) {
        // 定义一个集合保存扫描的类
        Set<Class<?>> clazzs = new LinkedHashSet<>();
        // 包的路径
        String packageDirName = packageName.replace('.', '/');

        Enumeration<URL> resources;
        try {
            // 加载包路径下的资源。
            resources = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                // 获取协议名称
                String protocol = url.getProtocol();
                // 文件系统
                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    scanFile(packageName, filePath, recursive, clazzs);
                }
                // jar 包
                else if ("jar".equals(protocol)) {
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    scanJar( packageName, recursive, jarFile, clazzs );
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return clazzs;
    }

    /**
     * 加载 jar 包中的类文件
     * @param packageName 要加载的包名
     * @param recursive 是否递归加载
     * @param jarFile jar 文件
     * @param clazzs 存放加载的类
     */
    private static void scanJar(String packageName, boolean recursive,
                                JarFile jarFile, Set<Class<?>> clazzs) {
        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
        while (jarEntryEnumeration.hasMoreElements()) {
            JarEntry jarEntry = jarEntryEnumeration.nextElement();
            String name = jarEntry.getName();
            if (name.startsWith("/")) {
                name = name.substring(1);
            }
            name = name.replace('/', '.');
            if (!name.startsWith(packageName)) {
                continue;
            }
            int index = name.lastIndexOf(".");
            // 不递归加载
            if ( index != packageName.length() && !recursive ) {
                continue;
            }
            if (!name.endsWith(".class") || jarEntry.isDirectory()) {
                continue;
            }

            String className = name.substring(0, name.length() - 6);
            try {
                clazzs.add(Thread.currentThread().getContextClassLoader().loadClass(className));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 加载文件系统中的类文件
     *
     * @param packageName 要加载的包名
     * @param packagePath 要加载的包路径
     * @param recursive 是否递归扫描
     * @param clazzs 存放加载的类
     */
    private static void scanFile(String packageName, String packagePath,
                                 boolean recursive, Set<Class<?>> clazzs) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            System.err.println(String.format("%s 不存在或不是一个目录", packagePath));
            return;
        }

        // 过滤目录下的 class 文件和目录
        File[] files = dir.listFiles(file ->
                (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
        if (files == null || files.length <= 0) {
            System.err.println(String.format("%s 目录中没有子目录和 class 文件", packagePath));
            return;
        }

        for (File file : files) {
            if (file.isDirectory()) {
                scanFile(packageName + "." + file.getName(),
                        file.getAbsolutePath(), recursive, clazzs);
            } else {
                String className = file.getName()
                        .substring(0, file.getName().length() - 6);
                try {
                    clazzs.add(Thread.currentThread().getContextClassLoader()
                            .loadClass(packageName + "." + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
